package com.lpf.flowable.controller;

import cn.hutool.core.collection.CollectionUtil;
import com.lpf.common.response.Result;
import org.apache.commons.io.IOUtils;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.*;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.runtime.Execution;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.identitylink.api.IdentityLink;
import org.flowable.image.ProcessDiagramGenerator;
import org.flowable.task.api.Task;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.ui.modeler.domain.Model;
import org.flowable.ui.modeler.serviceapi.ModelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/flowable/api")
public class FlowableApiController {

    // 流程引擎
    @Autowired
    private ProcessEngine processEngine;
    // 用户以及组管理服务
    @Autowired
    private IdentityService identityService;
    // 模型服务
    @Autowired
    private ModelService modelService;
    // 部署服务
    @Autowired
    private RepositoryService repositoryService;
    // 流程实例服务
    @Autowired
    private RuntimeService runtimeService;
    // 流程节点任务服务
    @Autowired
    private TaskService taskService;
    // 历史数据服务
    @Autowired
    private HistoryService historyService;

    /**
     * 流程部署
     *
     * @param modelId 流程ID，来自 ACT_DE_MODEL
     */
    @GetMapping(value = "/deploy/{modelId}")
    public Result<Deployment> deploy(@PathVariable(value = "modelId") String modelId) {

        Model modelData = modelService.getModel(modelId);

        byte[] bytes = modelService.getBpmnXML(modelData);
        if (bytes == null) {
            return Result.fail("模型数据为空，请先设计流程并成功保存，再进行发布");
        }

        BpmnModel model = modelService.getBpmnModel(modelData);
        if (model.getProcesses().size() == 0) {
            return Result.fail("数据模型不符要求，请至少设计一条主线流程");
        }

        byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(model);
        String processName = modelData.getName() + ".bpmn20.xml";

        // 部署流程
        Deployment deploy = repositoryService.createDeployment()
                .name(modelData.getName())
                .addBytes(processName, bpmnBytes)
                .deploy();

        return Result.data(deploy);
    }

    /**
     * 启动流程
     *
     * @param deployId    部署的流程 Id，来自 ACT_RE_PROCDEF
     * @param userId      用户 Id（流程的的发起人）
     * @param businessKey 数据 Key（业务键），一般为表单数据的 ID，仅作为表单数据与流程实例关联的依据
     */
    @GetMapping(value = "/start/{deployId}/{userId}/{businessKey}")
    public Result<Map<String, String>> start(
            @PathVariable(value = "deployId") String deployId,
            @PathVariable(value = "userId") String userId,
            @PathVariable(value = "businessKey") String businessKey) {

        // 设置发起人
        identityService.setAuthenticatedUserId(userId);
        // 根据流程 ID 启动流程
        ProcessInstance processInstance = runtimeService.startProcessInstanceById(deployId, businessKey);

        Map<String, String> result = new HashMap<>();
        result.put("ProcessDefinitionId", processInstance.getProcessDefinitionId());
        result.put("ProcessDefinitionName", processInstance.getProcessDefinitionName());
        result.put("Id", processInstance.getId());

        return Result.data(result);
    }

    /**
     * 获取当前候选组
     *
     * @param taskId 任务 Id，来自 ACT_RU_TASK
     */
    @GetMapping(value = "/taskInfo/{taskId}")
    public Result<List<String>> taskInfo(@PathVariable(value = "taskId") String taskId) {

        List<String> group = new ArrayList<>();

        List<IdentityLink> taskName = taskService.getIdentityLinksForTask(taskId);
        taskName.forEach(identityLink -> {
            group.add(identityLink.getGroupId());
        });

        return Result.data(group);
    }

    /**
     * 设置任务参数
     *
     * @param taskId 任务ID
     * @param map    键值对
     */
    @PostMapping(value = "/setVariables")
    public Result<String> setVariables(
            @RequestParam(value = "taskId") String taskId,
            @RequestBody Map<String, Object> map) {

        String processInstanceId = taskService.createTaskQuery().taskId(taskId).singleResult().getProcessInstanceId();
        runtimeService.setVariables(processInstanceId, map);

        return Result.success("设置成功");
    }

    /**
     * 设置任务参数
     *
     * @param taskId 任务ID
     * @param key    键
     * @param value  值
     */
    @PostMapping(value = "/setVariable")
    public Result<String> setVariable(
            @RequestParam(value = "taskId") String taskId,
            @RequestParam(value = "key") String key,
            @RequestParam(value = "value") Object value) {

        String processInstanceId = taskService.createTaskQuery().taskId(taskId).singleResult().getProcessInstanceId();
        runtimeService.setVariable(processInstanceId, key, value);

        return Result.success("设置成功");
    }

    /**
     * 设置任务参数
     *
     * @param taskId 任务ID
     * @param key    键
     * @param value  值
     */
    @PostMapping(value = "/setListVariable")
    public Result<String> setListVariable(
            @RequestParam(value = "taskId") String taskId,
            @RequestParam(value = "key") String key,
            @RequestParam(value = "value") List<String> value) {

        String processInstanceId = taskService.createTaskQuery().taskId(taskId).singleResult().getProcessInstanceId();
        runtimeService.setVariable(processInstanceId, key, value);

        return Result.success("设置成功");
    }

    /**
     * 任务处理
     *
     * @param taskId 任务 Id，来自 ACT_RU_TASK
     */
    @GetMapping(value = "/task/{taskId}")
    public Result<String> task(@PathVariable(value = "taskId") String taskId) {

        boolean isSuspended = taskService.createTaskQuery().taskId(taskId).singleResult().isSuspended();
        if (isSuspended) {
            return Result.fail("任务已挂起，无法完成");
        }

        // 完成任务
        taskService.complete(taskId);

        return Result.success("操作成功");
    }

    /**
     * 任务处理
     *
     * @param taskId   任务 Id，来自 ACT_RU_TASK
     * @param assignee 设置审核人
     * @param map      完成任务需要的条件参数
     */
    @PostMapping(value = "/task")
    public Result<String> taskByAssignee(
            @RequestParam(value = "taskId") String taskId,
            @RequestParam(value = "assignee") String assignee,
            @RequestBody Map<String, Object> map) {

        // 设置审核人
        taskService.setAssignee(taskId, assignee);

        // 设置任务参数，也可不设置：key value，只是示例
        // 带 Local 为局部参数，只适用于本任务，不带 Local 为全局任务，可在其他任务调用参数
        // taskService.setVariableLocal(taskId, "status", true);

        // 完成任务
        taskService.complete(taskId, map);

        return Result.success("操作成功");
    }

    /**
     * 中止流程
     *
     * @param processId 流程ID
     */
    @GetMapping(value = "/deleteProcess/{processId}")
    public Result<String> deleteProcess(@PathVariable(value = "processId") String processId) {
        runtimeService.deleteProcessInstance(processId, "中止流程");
        return Result.success("操作成功");
    }

    /**
     * 获取正在运行的 business_key 列表
     */
    @GetMapping(value = "/getRuntimeBusinessKey")
    public Result<List<String>> getRuntimeBusinessKey() {

        List<String> idList = new ArrayList<>();

        // 获取正在执行的任务列表
        List<Execution> list = runtimeService.createExecutionQuery().onlyProcessInstanceExecutions().list();

        list.forEach(execution -> {
            // 根据任务获取流程实例
            // 获取流程实例中的 business_key
            ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(execution.getProcessInstanceId()).singleResult();
            idList.add(pi.getBusinessKey());
        });

        return Result.data(idList);
    }

    /**
     * 根据用户，获取需要审核的业务键 business_key 列表
     *
     * @param userId 用户 Id
     */
    @GetMapping(value = "/getRuntimeBusinessKeyByUser/{userId}")
    public Result<List<Map<String, Object>>> getRuntimeBusinessKeyByUserId(@PathVariable(value = "userId") String userId) {

        List<Map<String, Object>> idList = new ArrayList<>();

        // 根据用户获取正在进行的任务
        List<Task> tasks = taskService.createTaskQuery().taskAssignee(userId).list();

        for (Task task : tasks) {
            Map<String, Object> data = new HashMap<>();
            // 根据任务获取流程实例
            ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();
            // 获取流程实例中的业务键
            data.put("businessKey", processInstance.getBusinessKey());
            // 获取任务 Id
            data.put("taskId", task.getId());
            // 流程定义名称
            data.put("processInstanceName", processInstance.getProcessDefinitionName());
            // 流程开始时间
            data.put("startTime", processInstance.getStartTime());
            idList.add(data);
        }

        return Result.data(idList);
    }

    /**
     * 根据组，获取需要审核的业务键 business_key 列表
     *
     * @param groupIds 组 Id
     */
    @PostMapping(value = "/getRuntimeBusinessKeyByGroup")
    public Result<List<Map<String, Object>>> getRuntimeBusinessKeyByGroup(@RequestBody List<String> groupIds) {

        List<Map<String, Object>> idList = new ArrayList<>();

        // 判断是否有组信息
        if (CollectionUtil.isNotEmpty(idList)) {
            // 根据发起人获取正在执行的任务列表
            List<Task> tasks = taskService.createTaskQuery().taskCandidateGroupIn(groupIds).list();
            tasks.forEach(task -> {
                Map<String, Object> data = new HashMap<>();
                // 根据任务获取流程实例
                ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();
                // 获取流程实例中的业务键
                data.put("businessKey", processInstance.getBusinessKey());
                // 获取任务 Id
                data.put("taskId", task.getId());
                // 流程定义名称
                data.put("processInstanceName", processInstance.getProcessDefinitionName());
                // 流程开始时间
                data.put("startTime", processInstance.getStartTime());
                idList.add(data);
            });
        }

        return Result.data(idList);
    }

    /**
     * 获取用户审核历史
     *
     * @param userId 发起人 Id
     */
    @GetMapping(value = "/getHistoryByUser/{userId}")
    public Result<List<Map<String, Object>>> getHistoryByUser(@PathVariable(value = "userId") String userId) {
        List<Map<String, Object>> historyList = new ArrayList<>();
        // 根据用户，查询任务实例历史
        List<HistoricTaskInstance> list = historyService.createHistoricTaskInstanceQuery().taskAssignee(userId).finished().orderByHistoricTaskInstanceEndTime().desc().list();
        list.forEach(historicTaskInstance -> {
            // 历史流程实例
            HistoricProcessInstance hpi = historyService.createHistoricProcessInstanceQuery().processInstanceId(historicTaskInstance.getProcessInstanceId()).singleResult();
            // 获取需要的历史数据
            Map<String, Object> historyInfo = new HashMap<>();
            historyInfo.put("assignee", historicTaskInstance.getAssignee());
            // 节点名称
            historyInfo.put("nodeName", historicTaskInstance.getName());
            // 流程开始时间
            historyInfo.put("startTime", historicTaskInstance.getCreateTime());
            // 节点操作时间（本流程节点结束时间）
            historyInfo.put("endTime", historicTaskInstance.getEndTime());
            // 流程定义名称
            historyInfo.put("processName", hpi.getProcessDefinitionName());
            // 流程实例 ID
            historyInfo.put("processInstanceId", historicTaskInstance.getProcessInstanceId());
            // 业务键
            historyInfo.put("businessKey", hpi.getBusinessKey());
            historyList.add(historyInfo);
        });

        return Result.data(historyList);
    }

    /**
     * 通过流程实例 Id，判断流程是否结束
     *
     * @param processInstanceId 流程实例 Id
     * @return true 结束，false 未结束
     */
    @GetMapping(value = "/checkProcessInstanceFinish/{processInstanceId}")
    public Result<Boolean> checkProcessInstanceFinish(@PathVariable(value = "processInstanceId") String processInstanceId) {
        boolean isFinish = false;
        // 根据流程 ID 获取未完成的流程中是否存在此流程
        long count = historyService.createHistoricProcessInstanceQuery().unfinished().processInstanceId(processInstanceId).count();
        // 不存在说明没有结束
        if (count == 0) {
            isFinish = true;
        }

        return Result.data(isFinish);
    }


    /**
     * 根据任务节点获取流程实例 Id
     *
     * @param taskId 任务节点 Id
     */
    @GetMapping(value = "/getProcessInstanceIdByTaskId/{taskId}")
    public Result<String> getProcessInstanceIdByTaskId(@PathVariable(value = "taskId") String taskId) {
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        return Result.data(task.getProcessInstanceId());
    }

    /**
     * 根据流程实例 ID 获取任务进度流程图
     *
     * @param processInstanceId 流程实例 Id
     */
    @RequestMapping(value = "/getProcessDiagram/{processInstanceId}", method = RequestMethod.GET)
    public void getProcessDiagram(@PathVariable(value = "processInstanceId") String processInstanceId, HttpServletResponse httpServletResponse) {
        // 流程定义 ID
        String processDefinitionId;

        // 查看完成的进程中是否存在此进程
        long count = historyService.createHistoricProcessInstanceQuery().finished().processInstanceId(processInstanceId).count();
        if (count > 0) {
            // 如果流程已经结束，则得到结束节点
            HistoricProcessInstance pi = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
            processDefinitionId = pi.getProcessDefinitionId();
        } else {
            // 如果流程没有结束，则取当前活动节点
            // 根据流程实例ID获得当前处于活动状态的ActivityId合集
            ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
            processDefinitionId = pi.getProcessDefinitionId();
        }

        // 获得活动的节点
        List<HistoricActivityInstance> highLightedActivityList = historyService
                .createHistoricActivityInstanceQuery()
                .processInstanceId(processInstanceId)
                .orderByHistoricActivityInstanceStartTime()
                .asc()
                .list();

        List<String> highLightedActivities = new ArrayList<>();
        for (HistoricActivityInstance tempActivity : highLightedActivityList) {
            String activityId = tempActivity.getActivityId();
            highLightedActivities.add(activityId);
        }

        List<String> flows = new ArrayList<>();

        // 获取流程图
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
        ProcessEngineConfiguration processEngineConfig = processEngine.getProcessEngineConfiguration();
        ProcessDiagramGenerator diagramGenerator = processEngineConfig.getProcessDiagramGenerator();

        InputStream in = diagramGenerator.generateDiagram(
                bpmnModel,
                "bmp",
                highLightedActivities,
                flows,
                processEngineConfig.getActivityFontName(),
                processEngineConfig.getLabelFontName(),
                processEngineConfig.getAnnotationFontName(),
                processEngineConfig.getClassLoader(),
                1.0,
                true);

        OutputStream out = null;
        byte[] buf = new byte[1024];
        int legth;
        try {
            out = httpServletResponse.getOutputStream();
            while ((legth = in.read(buf)) != -1) {
                out.write(buf, 0, legth);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(out);
            IOUtils.closeQuietly(in);
        }
    }

    /**
     * 根据任务 ID 获取任务进度流程图
     *
     * @param taskId 任务节点 Id
     */
    @GetMapping(value = "/getTaskProcessDiagram/{taskId}")
    public void getTaskProcessDiagram(@PathVariable(value = "taskId") String taskId, HttpServletResponse httpServletResponse) {

        // 根据任务 ID 获取流程实例 ID
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        String processInstanceId = task.getProcessInstanceId();

        // 根据流程实例获取流程图
        // 流程定义 ID
        String processDefinitionId;

        // 查看完成的进程中是否存在此进程
        long count = historyService.createHistoricProcessInstanceQuery().finished().processInstanceId(processInstanceId).count();
        if (count > 0) {
            // 如果流程已经结束，则得到结束节点
            HistoricProcessInstance pi = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
            processDefinitionId = pi.getProcessDefinitionId();
        } else {
            // 如果流程没有结束，则取当前活动节点
            // 根据流程实例ID获得当前处于活动状态的ActivityId合集
            ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
            processDefinitionId = pi.getProcessDefinitionId();
        }

        // 获得活动的节点
        List<HistoricActivityInstance> highLightedActivitList = historyService
                .createHistoricActivityInstanceQuery()
                .processInstanceId(processInstanceId)
                .orderByHistoricActivityInstanceStartTime()
                .asc()
                .list();

        List<String> highLightedActivities = new ArrayList<>();
        for (HistoricActivityInstance tempActivity : highLightedActivitList) {
            String activityId = tempActivity.getActivityId();
            highLightedActivities.add(activityId);
        }

        List<String> flows = new ArrayList<>();
        //获取流程图
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
        ProcessEngineConfiguration processEngineConfig = processEngine.getProcessEngineConfiguration();
        ProcessDiagramGenerator diagramGenerator = processEngineConfig.getProcessDiagramGenerator();

        InputStream in = diagramGenerator.generateDiagram(
                bpmnModel,
                "bmp",
                highLightedActivities,
                flows,
                processEngineConfig.getActivityFontName(),
                processEngineConfig.getLabelFontName(),
                processEngineConfig.getAnnotationFontName(),
                processEngineConfig.getClassLoader(),
                1.0,
                true);

        OutputStream out = null;
        byte[] buf = new byte[1024];
        int legth;
        try {
            out = httpServletResponse.getOutputStream();
            while ((legth = in.read(buf)) != -1) {
                out.write(buf, 0, legth);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(out);
            IOUtils.closeQuietly(in);
        }
    }

}